package utfpr.si.core;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
/*
 * John Theo
 * Solano Alves
 * 2011
 */
public class Population {
	private final int INITIAL_POPULATION = 50;
	private final int CROSSOVER_POPULATION = 30; //Sempre um numero par, pois Ž o numero de individuos para cruzar
	private ArrayList<Citizen> population;
	private ArrayList<Citizen> selectedPopulation;
	private final double ELITISM = 1;
	private final double MUTATION_RATE = 0.01;		
	private final int [] ALLELES = {1,2,4,8,16,32,64,128};
	private ArrayList<Integer> genes = new ArrayList<Integer>();
	private ArrayList<Integer> historico;
	private BufferedWriter logIndividuos = null;
	private BufferedWriter logHistorico = null;
	/*
	 * Inicializa a população vazia
	 */
	public Population(){
		this.population = new ArrayList<Citizen>();
		this.selectedPopulation = new ArrayList<Citizen>();
		this.historico = new ArrayList<Integer>();
		genes.clear(); 
		
		/* Arquivo para log */
		try {
			this.logIndividuos = new BufferedWriter(new FileWriter("logIndividuos.txt"));
			this.logHistorico = new BufferedWriter(new FileWriter("logHistorico.txt"));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addIndividuoLog(String individuo){
		try {
			this.logIndividuos.write(individuo);
			this.logIndividuos.newLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void addHistoricoLog(String historico){
		try {
			this.logHistorico.write(historico);
			this.logHistorico.newLine();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void closeFiles(){
		try {
			this.logHistorico.close();
			this.logIndividuos.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void loadGenes(){
		genes.add(1);
		genes.add(2);
		genes.add(4);
		genes.add(8);
		genes.add(16);
		genes.add(32);
		genes.add(64);
		genes.add(128);
	}
	
	/*
	 * Cria população de individuos
	 */
	public void createPopulation(){
		int chromossome[] = new int[8];
		Random r = new Random();
		for (int i = 0; i < INITIAL_POPULATION; i++) {
			int gerado=0;
			if(r.nextDouble() < ELITISM){
				//Gera uma população sem repetição de genes e sem repetição de indivíduos
				do{
					loadGenes();
					for (int j = 0; j < chromossome.length; j++) {
						if(!genes.isEmpty()){
							gerado=r.nextInt(genes.size());
							chromossome[j] = genes.get(gerado);
							genes.remove(gerado);
						}
					}
				}while(this.population.contains(chromossome));
				this.population.add(new Citizen(chromossome));
				
			}else{
				for (int j = 0; j < chromossome.length; j++) {
					chromossome[j] = ALLELES[ r.nextInt(ALLELES.length) ];
				}
				this.population.add(new Citizen(chromossome));
			}
		}
	}
	
	public Citizen getSolution(){
		int auxfit = 0;
		for(Citizen citizen : population){
			if(citizen.getFitness()>auxfit)
				auxfit = citizen.getFitness();
		}
//		System.out.println("Melhor fitness: "+auxfit);
		this.addIndividuoLog("Melhor fitness: "+auxfit);
		this.addHistoricoLog(auxfit+";");
		historico.add(auxfit);
		
		for(Citizen citizen : population){
			if( citizen.getFitness() == 100 )//valor 100 definido em Citizen
				return citizen;
		}
		return null;
	}
	
	public void doSelection(){
		Citizen auxCitizen = new Citizen();
		selectedPopulation.clear();
//		Random r = new Random();
		for(int i = 0; i< CROSSOVER_POPULATION; i++){
//			if(r.nextDouble() < ELITISM){ //seleciona o melhor
//				auxCitizen = selectElite();
//				selectedPopulation.add(auxCitizen[0]);
//				population.remove(auxCitizen[0]);
//				selectedPopulation.add(auxCitizen[1]);
//				population.remove(auxCitizen[1]);
//			}else{ //seleciona o probabilistico
				auxCitizen = selectCitizen();
				selectedPopulation.add(auxCitizen);
				//population.remove(auxCitizen);
//			}
		}
//		System.out.println("\n== SELECAO ==");
//		System.out.println("* POPULACAO");
		this.addIndividuoLog("\n== SELECAO ==");
		this.addIndividuoLog("* POPULACAO");
		for(Citizen citizen : population){
//			System.out.println("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
			this.addIndividuoLog("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
		}
		
//		System.out.println("* SELECIONADOS");
		this.addIndividuoLog("* SELECIONADOS");
		for(Citizen citizen : selectedPopulation){
//			System.out.println("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
			this.addIndividuoLog("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
		}
	}
	
	@SuppressWarnings("unchecked")
	public void doCrossover(){
		//Loop percorre de 2 em dois para fazer crossover
		for(int i = 0; i < selectedPopulation.size()/2 ; i+=2){
			int crossPoint = (new Random()).nextInt(ALLELES.length - 1) + 1;//soma mais um para caso seja sorteado 0, haja particao para esquerda
			
			//Ponteiro que irá percorrer o cromossomo para fazer o crossover
			int j = 0;
			Citizen citizenAux = new Citizen(selectedPopulation.get(i).getChromossome());
			//Crossover entre 2 cromossomos
			for( j = 0; j < crossPoint; j++){
				//Pega o locus da posi�‹o j do primeiro elemento do par e joga para o segundo
				selectedPopulation.get(i).setLocus(j, selectedPopulation.get(i+1).getLocus(j));				
				selectedPopulation.get(i+1).setLocus(j, citizenAux.getLocus(j));
			}
		}
		//this.population.clear();
		
		
		//population = (ArrayList<Citizen>) selectedPopulation.clone();
//		System.out.println("\n== CROSSOVER ==");
//		System.out.println("* POPULACAO CRUZADA");
		this.addIndividuoLog("\n== CROSSOVER ==");
		this.addIndividuoLog("* POPULACAO CRUZADA");
		for(Citizen citizen : selectedPopulation){
//			System.out.println("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
			this.addIndividuoLog("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
		}
		
		for(Citizen citizen : (ArrayList<Citizen>) selectedPopulation.clone()){
			population.add(citizen);
		}
		
	}
	
	
	public void doMutation(){
		Random r = new Random();
		for (Citizen citizen : population){
			for (int j = 0;j<ALLELES.length;j++){
				if(r.nextDouble() < MUTATION_RATE){
					citizen.setLocus(j, ALLELES[r.nextInt(ALLELES.length)]);
				}
			}
		}
//		System.out.println("\n== MUTAÇÃO ==");
//		System.out.println("* POPULACAO MUTADA");
		this.addIndividuoLog("\n== MUTAÇÃO ==");
		this.addIndividuoLog("* POPULACAO MUTADA");
		for(Citizen citizen : population){
//			System.out.println("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
			this.addIndividuoLog("Individuo: "+citizen.getDescriptor()+" Fitness: "+citizen.getFitness());
		}
		
	}
	
	public void doNaturalSelection(){
		HashSet hs = new HashSet();
		hs.addAll(population);
		population.clear();
		population.addAll(hs);
	}
	
	
	/*
	 * Método para elitismo, seleciona o melhor casal da população 
	 */
	
	@SuppressWarnings("unchecked")
	public Citizen[] selectElite(){
		Citizen[] bestCitizen = new Citizen[2];
		int bestFitness;
		ArrayList<Citizen> populationAux = new ArrayList<Citizen>();
		populationAux = (ArrayList<Citizen>) population.clone();		
		for (int i = 0; i < 2; i++) {
			bestFitness = 100;
			for(Citizen citizen : populationAux){
				int fitness = citizen.getFitness();
				if(fitness < bestFitness){
					bestFitness = fitness;
					bestCitizen[i] = citizen;
				}
			}
			populationAux.remove(bestCitizen[i]);
		}
		return bestCitizen;
	}
	
	/*
	 * Método para seleção de individuo com base no seu fitness
	 */
	public Citizen selectCitizen(){
//		double prob = 0.0;
		double r = 0.0;
		int citizen = 0;
		double soma = 0.0;
		if( population.size() > 1){
			//Seleciona o individuo no momento que a soma ultrapassa o random
			do{
				r = Math.random();
			}while( r == 0.0 || r == 1.0);
			
			do{
				soma += this.getProbCitizen(population.get(citizen));
				citizen++;
			}while(soma<r && citizen<population.size());
		}
		return population.get(citizen - 1);

	}
	
	public double getProbCitizen(Citizen citizen){
		int sum = 0; // somatorio dos fitness da populacao
		for(Citizen c : population){
			sum += c.getFitness();
		}
		return (double)(citizen.getFitness())/sum;
	}

	public ArrayList<Integer> getHistorico() {
		return historico;
	}

	public void setHistorico(ArrayList<Integer> historico) {
		this.historico = historico;
	}
	
	
}
